#include "World.h"
#include "Grid.h"
#include "Chunk.h"
#include "../Managers/GameManager.h"
#include "../GameObjects/Player.h"
#include <sstream>

World::World()
{
}

World::~World()
{
}

void World::init(const Vec2i &viewport)
{
	chunk_size = viewport;

	ran = std::make_shared<TCODRandom>(103);
	noise = std::make_shared<TCODNoise>(2, ran.get());

	grid = std::make_shared<Grid>();

	for(int x = -1; x <= 1; x++)
		for(int y = -1; y <= 1; y++)
			generateChunk(grid->getChunk(Vec2i(x,y)), Vec2i(x*chunk_size.x(), y*chunk_size.y()));
}

void World::generateChunk(ChunkPtr &chunk, const Vec2i &pos)
{
	static int smooth_dx[] = { -1,		-1,		-1,		 0,		0,		0,		 1,		1,		1 };
	static int smooth_dy[] = { -1,		 0,		 1,		-1,		0,		1,		-1,		0,		1 };
	static float weights[] = {  0.63f,	 0.63f,	 0.63f,	 0.63f,	0.63f,	0.63f,	 0.63f,	0.63f,	0.63f };

	if(chunk == nullptr)
		chunk = std::make_shared<Chunk>(chunk_size, pos);
	if(chunk->isGenerated() == false)
		chunk->generate(noise, ran, 200.0f, 30.0f, 5.0f, &smooth_dx[0], &smooth_dy[0], &weights[0]);
}

void World::render()
{
	for(int x = -1; x <= 1; x++)
	{
		for(int y = -1; y <= 1; y++)
		{
			auto &chunk = grid->getChunk(Vec2i(x,y));
			if(chunk)
				chunk->render();
		}
	}
}

void World::update()
{
	Vec2i direction_data;
	bool walking_west, walking_north;
	auto &center = grid->getChunk(Vec2i(0,0));
	if(center == nullptr)
		return;
	
	//If this happens, a new CENTER chunk should be defined!
	if(Vec2i::overlap(	center->getLowerBounds(), center->getUpperBounds(), 
						GameManager::getSingleton()->getPlayer()->getLowerBounds(), GameManager::getSingleton()->getPlayer()->getUpperBounds(), 
						direction_data, walking_west, walking_north) == false)
	{
		grid->move(next_center_pos);
		return;
	}

	//if not, then we need to update where the player is on the center chunk, so that we can better guess
	//which chunks to cache, and where the next center chunk might move
	updatePlayerOnCenter(direction_data, walking_west, walking_north);
}

void World::updatePlayerOnCenter(const Vec2i direction_data, bool walking_west, bool walking_north)
{
	if(findNextCenter(direction_data, walking_west, walking_north))
		return;

	checkCacheThreshold(direction_data, walking_west, walking_north);
}

bool World::findNextCenter(const Vec2i direction_data, bool walking_west, bool walking_north)
{
	if(direction_data.x() <= 1)
		if(walking_west)	next_center_pos = Vec2i(-1, 0);
		else				next_center_pos = Vec2i(1, 0);
	else if(direction_data.y() <= 1)
		if(walking_north)	next_center_pos = Vec2i(0, -1);
		else				next_center_pos = Vec2i(0, 1);
	else return false;
	return true;
}

void World::checkCacheThreshold(const Vec2i direction_data, bool walking_west, bool walking_north)
{
	static int CACHE_THRESHOLD = 15;
	if(direction_data.x() == CACHE_THRESHOLD)
		if(walking_west)	loadChunk(Vec2i(-1, 0));
		else				loadChunk(Vec2i(1, 0));
	if(direction_data.y() == CACHE_THRESHOLD)
		if(walking_north)	loadChunk(Vec2i(0, -1));
		else				loadChunk(Vec2i(0, 1));
}

void World::loadChunk(const Vec2i &pos)
{
	const Vec2i &center_pos = grid->getChunk(Vec2i(0,0))->getPos();
	if(pos.x() != 0)
	{
		generateChunk(grid->getChunk(Vec2i(pos.x()*2,-1)), center_pos + Vec2i(pos.x()*2*chunk_size.x(),-1*chunk_size.y()));
		generateChunk(grid->getChunk(Vec2i(pos.x()*2, 0)), center_pos + Vec2i(pos.x()*2*chunk_size.x(), 0));
		generateChunk(grid->getChunk(Vec2i(pos.x()*2, 1)), center_pos + Vec2i(pos.x()*2*chunk_size.x(), 1*chunk_size.y()));
	}
	else if(pos.y() != 0)
	{
		generateChunk(grid->getChunk(Vec2i(-1,pos.y()*2)), center_pos + Vec2i(-1*chunk_size.x(),	pos.y()*2*chunk_size.y()));
		generateChunk(grid->getChunk(Vec2i( 0,pos.y()*2)), center_pos + Vec2i( 0,				pos.y()*2*chunk_size.y()));
		generateChunk(grid->getChunk(Vec2i( 1,pos.y()*2)), center_pos + Vec2i( 1*chunk_size.x(),	pos.y()*2*chunk_size.y()));
	}
}
